package com.cn.file;

import com.cn.util.UtilAll;

import java.io.File;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 底层存储结构
 */
public class MapedFileQueue {

    /**
     * 文件存储路径
     */
    private final String storePath;

    /**
     * 文件大小
     */
    private final long mappedFileSize;


    //快速定位等待刷盘的文件
    private long flushedWhere = 0;
    //快速定位等待刷盘的文件
    private int committedWhere = 0;

    private final CopyOnWriteArrayList<MappFile> mappedFiles = new CopyOnWriteArrayList<>();
    //存储时间
    private volatile long storeTimestamp = 0;


    public MapedFileQueue(String storePath, long mappedFileSize) {
        this.storePath = storePath;
        this.mappedFileSize = mappedFileSize;
    }

    public void load() {
        File filed = new File(this.storePath);
        File[] files = filed.listFiles();
        if (files != null) {
            for (File file : files) {
                MappFile mappFile = new MappFile(file.getPath(), this.mappedFileSize);
                //设置文件大小为默认提交位置
                mappFile.setFlushedPosition(mappFile.getFileSize());
                mappFile.setCommittedPosition(mappFile.getFileSize());
                //设置文件大小为默认可写入位置（位移恢复服务会检测重置该参数为正确的值）
                mappFile.setWrotePosition(mappFile.getFileSize());
                this.mappedFiles.add(mappFile);
            }
        }
        if (this.mappedFiles.size() == 0) {
            getLastMappedFile(0, true);
        }

    }

    /**
     * 处理commitlog文件的可写入位置
     */
    public void recover(long offset) {
        //以下代码是临时方案，等待完善（目前代码是恢复了所有的队列文件，如果文件多会很影响初始化加载速度，因此需要优化）
        for (MappFile mappFile : mappedFiles) {
            mappFile.recoverFile();
        }
    }

    /**
     * 处理consumequeue文件的可写入位置
     *
     * @param offset
     */
    public void truncateDirtyFiles(long offset) {
        for (MappFile mappFile : mappedFiles) {
            //文件的最大位移
            long maxoffset = mappFile.getFileFromOffset() + this.mappedFileSize;
            if (offset > maxoffset) {
                continue;
            }
            /**
             * 假设文件大小限定为12字节，1个文件可以存3条消息
             * 假设 offset = 8， 此时 filefromoffset=0， 那么 8%12= wrotepoition=8；
             * 如果 offset = 16，此时 filefromoffset=12，那么16%12= wrotepotion=4
             */
            if (offset == mappFile.getFileSize()) {
                mappFile.setWrotePosition(offset);
            } else {
                mappFile.setWrotePosition(offset % mappFile.getFileSize());
            }
            mappFile.setFlushedPosition(offset % mappFile.getFileSize());
            mappFile.setCommittedPosition(offset % mappFile.getFileSize());
        }
    }

    /**
     * 获取当前数据文件的可写入位置
     *
     * @return
     */
    public long getMaxOffset() {
        MappFile mappFile = getLastMappedFile();
        if (mappFile != null) {
            long maxOffset = mappFile.getFileFromOffset() + mappFile.getReadPosition();
            return maxOffset;
        }
        return 0;
    }

    /**
     * 通过刷盘位移来查找mappfile
     *
     * @param offset
     * @param returnFirstOnNotFound
     * @return
     */
    public MappFile findMappedFileByOffset(long offset, boolean returnFirstOnNotFound) {
        MappFile firstMappFile = this.getFirstMappedFile();//第一个文件
        MappFile lastMappFile = this.getLastMappedFile();//最后一个文件
        if (firstMappFile != null && lastMappFile != null) {
            //等待刷盘的位移不正确（每个文件的名字就是位移）
            if (offset < firstMappFile.getFileFromOffset() || (offset > lastMappFile.getFileFromOffset() + this.mappedFileSize)) {
                System.out.println("文件没有找到，实际位移：" + offset + "，当前最大位移：" + lastMappFile.getFileFromOffset());
                return null;
            } else {
                /**
                 * 获取等待刷盘的文件
                 * 原理：
                 * 0. 每个数据文件的名字就是位移
                 * 1. 假设一个文件大小是 1024;
                 * 2. offset 是1023，小于1024，那么肯定是第一个数据文件
                 * 3. offset 是1025，大于1024，那么说明第一个数据文件以及满了，在第二个文件里面。 1
                 */
                //确定在第几个文件里面
                int index = (int) ((offset / this.mappedFileSize) - (firstMappFile.getFileFromOffset() / this.mappedFileSize));
                MappFile targetMappfile = null;
                if (this.mappedFiles.size() == 0) {
                    return null;
                }
                targetMappfile = this.mappedFiles.get(index);
                if (targetMappfile != null && offset >= targetMappfile.getFileFromOffset() && offset <= (targetMappfile.getFileFromOffset() + this.mappedFileSize)) {
                    return targetMappfile;
                }
                //从所有数据文件里面获取
                for (MappFile mappFile : this.mappedFiles) {
                    if (offset >= mappFile.getFileFromOffset() && offset <= (mappFile.getFileFromOffset() + this.mappedFileSize)) {
                        return mappFile;
                    }
                }

            }
        }
        return null;
    }


    public MappFile getLastMappedFile(long start) {
        return getLastMappedFile(start, true);
    }

    public MappFile getLastMappedFile(long startOffset, boolean needcreate) {
        long createOffset = -1;
        MappFile mappFile = getLastMappedFile();
        //如果为空(不存在文件)
        if (null == mappFile) {
            createOffset = startOffset - (startOffset % this.mappedFileSize);
        }
        //如果已经存在了文件，并且文件已经被写满，那么重置createOffset
        if (mappFile != null && mappFile.ifFull()) {
            createOffset = mappFile.getFileFromOffset() + this.mappedFileSize;
        }
        if (createOffset != -1 && needcreate) {
            String fileName = this.storePath + UtilAll.offset2FileName(createOffset);
            mappFile = new MappFile(fileName, this.mappedFileSize);
            mappedFiles.add(mappFile);
        }

        return mappFile;
    }


    /**
     * 查找第一个文件
     *
     * @return
     */
    public MappFile getFirstMappedFile() {
        if (!this.mappedFiles.isEmpty()) {
            return this.mappedFiles.get(0);
        }
        return null;
    }


    public MappFile getLastMappedFile() {
        MappFile mappFile = null;
        while (!this.mappedFiles.isEmpty()) {
            try {
                mappFile = this.mappedFiles.get(this.mappedFiles.size() - 1);
                break;
            } catch (Exception e) {
                break;
            }
        }
        return mappFile;
    }

    public boolean flush(final int flushLeastPages) {
        MappFile mappFile = findMappedFileByOffset(this.flushedWhere, false);
        if (mappFile != null) {
            long result = mappFile.flush(flushLeastPages);
//            System.out.println("刷盘完成，当前位置：" + this.flushedWhere);
            if (result == -1) {
                return false;
            }
            this.flushedWhere = result;
            return true;
        }
        return false;
    }

    public long getFlushedWhere() {
        return flushedWhere;
    }

    public void setFlushedWhere(int flushedWhere) {
        this.flushedWhere = flushedWhere;
    }

    public int getCommittedWhere() {
        return committedWhere;
    }

    public void setCommittedWhere(int committedWhere) {
        this.committedWhere = committedWhere;
    }

    public long getStoreTimestamp() {
        return storeTimestamp;
    }

    public void setStoreTimestamp(long storeTimestamp) {
        this.storeTimestamp = storeTimestamp;
    }

    public String getStorePath() {
        return storePath;
    }

    public CopyOnWriteArrayList<MappFile> getMappedFiles() {
        return mappedFiles;
    }
}
